package com.example.demo.service.impl;

import com.example.demo.dao.StudentMapper;
import com.example.demo.pojo.Student;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.example.demo.dao.ConfigProvinceMapper;
import com.example.demo.pojo.ConfigProvince;
import com.example.demo.pojo.LogSendProvince;

import com.example.demo.service.CrossProvinceService;
import com.example.demo.service.LogSendProvinceService;
import com.example.demo.utils.CompletableFutureExpandUtils;
import com.example.demo.vo.ProvinceResult;
import com.example.demo.vo.ResponseResult;
import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import static java.util.concurrent.TimeUnit.SECONDS;

@Slf4j
@Service("CrossProvinceService")
public class CrossProvinceServiceImpl implements CrossProvinceService {

    private final ObjectMapper objectMapper;

    private final RestTemplate restTemplate;

    @Resource
    private StudentMapper studentMapper;

    @Resource
    private ConfigProvinceMapper configProvinceMapper;

    @Resource
    private LogSendProvinceService logSendProvinceService;

    @Resource
    @Qualifier("asyncTaskExecutor")
    private ThreadPoolTaskExecutor asyncTaskExecutor;

    @Resource
    @Qualifier("asyncTaskExecutor1")
    private ThreadPoolTaskExecutor asyncTaskExecutor1;

    public CrossProvinceServiceImpl(ObjectMapper objectMapper, RestTemplate restTemplate) {
        this.objectMapper = objectMapper;
        this.restTemplate = restTemplate;
    }


    private <T> ProvinceResult sendToSingleProvince(ConfigProvince privince, T content){
        log.info("调用省侧任务" + privince.getUrl() + " is running on thread: " + Thread.currentThread().getName());
        HttpHeaders uploadHeaders = new HttpHeaders();
        if (content instanceof ByteArrayResource){  //文件字节流
            uploadHeaders.setContentType(new MediaType(MediaType.MULTIPART_FORM_DATA, StandardCharsets.UTF_8));
            MultiValueMap<String,Object> params = new LinkedMultiValueMap<>();
            params.add("file", content);
            HttpEntity<MultiValueMap<String,Object>> uploadRequest = new HttpEntity<>(params, uploadHeaders);
            return restTemplate.postForObject(privince.getUrl(), uploadRequest, ProvinceResult.class);
        } else {
            uploadHeaders.setContentType(new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8));
            HttpEntity<T> uploadRequest = new HttpEntity<T>(content, uploadHeaders);
            return restTemplate.postForObject(privince.getUrl(), uploadRequest, ProvinceResult.class);
        }
    }

    @Override
    @Transactional(transactionManager = "transactionManager",propagation = Propagation.REQUIRED, rollbackFor = Exception.class, isolation= Isolation.DEFAULT)
    public <T> List<ResponseResult> sendToProvinces(List<ConfigProvince> privinces, T content, String id)  {
        //采用多线程并行发送，提高性能
        List<CompletableFuture<ResponseResult>> futureList = new ArrayList<>();

        for (ConfigProvince province : privinces) {
            CompletableFuture<ProvinceResult> future =  CompletableFuture.supplyAsync(
                    () -> sendToSingleProvince(province,content),asyncTaskExecutor
            );
            CompletableFutureExpandUtils.orTimeout(future,3,SECONDS);   //设置服务调用超时时间

            CompletableFuture<ResponseResult> future1 =  future.handle((res, ex) -> {
                if(ex != null) {
                    res = new ProvinceResult();   //调用服务失败 res 为null
                    res.setSuccessful(false);
                    res.setResultHint(ex.getMessage());
                    log.info("发生异常 - " + ex.getMessage());
                }
                return res;
            }).thenApplyAsync(p->{

                log.info("入库任务" + province.getUrl() + " is running on thread: " + Thread.currentThread().getName());

                // 写发送日志
                ResponseResult res ;
                LogSendProvince logSendProvince = new LogSendProvince();
                logSendProvince.setProvinceOrgId(province.getProvinceOrgId());
                logSendProvince.setProvinceOrgName(province.getProvinceOrgName());
                logSendProvince.setBusinessId(id);
                if (p.getSuccessful()) {
                    logSendProvince.setSendSucess("1");
                    logSendProvince.setSendSucessDatetime(LocalDateTime.now());
                    logSendProvinceService.createLogSendProvince(logSendProvince);
                    res = ResponseResult.success(province.getProvinceOrgName()+"：下发成功") ;
              } else { //调用失败，比如连不上对方服务，或者对方返回处理失败等
                    logSendProvince.setSendSucess("0");
                    logSendProvince.setSendSucessDatetime(LocalDateTime.now());
                    logSendProvinceService.createLogSendProvince(logSendProvince);
                    res =  ResponseResult.fail(province.getProvinceOrgName()+"：下发失败"+p.getResultHint()) ;
                }
                return  res;
            },asyncTaskExecutor1);

            futureList.add(future1);
        }

        //聚合 等待一起完成！
        List<ResponseResult> lists = futureList.stream().map(CompletableFuture::join).collect(Collectors.toList());
        return lists;
    }

    public List<ConfigProvince> getProvinceByIds(String ids){
        String[] provinceids = ids.split(",");
        List<String> list = new ArrayList<>(Arrays.asList(provinceids));
        return configProvinceMapper.selectByIds(list);
    }

    public Student getStudent(String id) {
        return studentMapper.selectByPrimaryKey(id);
    }

}
